package com.ag.improvedjava.iterable;

import java.util.Comparator;
import java.util.Iterator;
import java.util.NoSuchElementException;

import com.ag.improvedjava.utils.Function;
import com.ag.improvedjava.utils.Function2;
import com.ag.improvedjava.utils.Lookup;

final class GroupJoinIterable<K, T, U, R> implements Iterable<R> {

	private final Iterable<T> outer;
	private final Function<T, K> outerKeySelector;
	private final Iterable<U> inner;
	private final Function<U, K> innerKeySelector;
	private final Function2<T, Iterable<U>, R> resultSelector;
	private final Comparator<K> comparer;

	GroupJoinIterable(Iterable<T> outer,
			Function<T, K> outerKeySelector, Iterable<U> inner,
			Function<U, K> innerKeySelector,
			Function2<T, Iterable<U>, R> resultSelector, Comparator<K> comparer) {
		this.outer = outer;
		this.outerKeySelector = outerKeySelector;
		this.inner = inner;
		this.innerKeySelector = innerKeySelector;
		this.resultSelector = resultSelector;
		this.comparer = comparer;
	}

	@Override
	public Iterator<R> iterator() {
		Lookup<K, U> innerLookup = Iterables.convert(inner).toLookup(innerKeySelector, comparer);
		return new GroupJoinIterator<>(outer.iterator(), outerKeySelector, resultSelector, innerLookup);
	}
}

final class GroupJoinIterator<K, T, U, R> extends UnmodifiableIterator<R> {

	private final Iterator<T> outer;
	private final Function<T, K> outerKeySelector;
	private final Function2<T, Iterable<U>, R> resultSelector;
	private final Lookup<K, U> innerLookup;
	
	GroupJoinIterator(Iterator<T> outer,
			Function<T, K> outerKeySelector,
			Function2<T, Iterable<U>, R> resultSelector,
			Lookup<K, U> innerLookup) {
		this.outer = outer;
		this.outerKeySelector = outerKeySelector;
		this.resultSelector = resultSelector;
		this.innerLookup = innerLookup;
	}

	@Override
	public boolean hasNext() {
		return outer.hasNext();
	}

	@Override
	public R next() {
		if (!hasNext()) {
			throw new NoSuchElementException();
		}
		T outerElement = outer.next();
		K key = outerKeySelector.execute(outerElement);
		return resultSelector.execute(outerElement, innerLookup.values(key));
	}

}